#pragma once

#include "Tokenizer.hpp"
#include "../Shared/GObjHelpers.hpp"
#include "../Shared/StringBuilder.hpp"
#include "../Shared/Exception.hpp"

#include <glibmm.h>

namespace CPreProc
{
  class Macro : public BuildNG::Object
  {
  public:
    Macro(const Glib::ustring &symbol_name);
    Macro(const Glib::ustring &symbol_name,const Glib::ustring &value);
    Macro(const Glib::ustring &symbol_name,const Glib::ustring &value,const std::vector<Glib::ustring> &parameters);

    static Glib::RefPtr<Macro>  parse(Tokenizer &tokenizer,bool directive_read);
    static Glib::RefPtr<Macro>  parse(const Glib::ustring &line,bool directive_read);

    const Glib::ustring&        get_symbol_name() const { return symbol_name; }
    bool                        get_has_param_list() const { return has_param_list; }
    const std::vector<Token>&   get_replacement_list() const { return replacement_list; };

    int                         get_num_params() const { return (int)parameters.size(); }
    const Glib::ustring         get_param_name(int i) const { return parameters[i]; }

    bool                        get_has_var_args() { return has_var_args; }

    bool                        replacement_lists_equal(Glib::RefPtr<Macro> b);

  private:
                                Macro(Tokenizer &tokenizer,bool directive_read);
                                Macro(const Glib::ustring &line,bool directive_read,int hack_arg);

    void                        parse_impl(Tokenizer &tokenizer,bool directive_read);

    void                        parse_param_list(Tokenizer &tokenizer);
    void                        parse_replacement_list(Tokenizer &tokenizer);

    Glib::ustring               symbol_name;
    bool                        has_param_list;
    bool                        has_var_args;
    std::vector<Glib::ustring>  parameters;
    // The tokenized replacement list.
    // NOTE: Whitespace must be collapsed into a single
    // space here, and no two whitespace tokens can follow each other.
    // It can't start or end with whitespace either. This is to make 
    // sure that the result will have whitespace in it's correct manner.
    std::vector<Token>          replacement_list;

    // optional.
    Glib::ustring               line;
  };

  class IMacroProvider
  {
  public:
    virtual Glib::RefPtr<Macro> get_macro(const Glib::ustring &name) const = 0;
  };

  Glib::ustring apply_macros(const Glib::ustring &line,const IMacroProvider &macro_provider);
  void apply_macros(const gchar *in_begin,const gchar *in_end,
    BuildNG::StringBuilder<2048> &output,const IMacroProvider &macro_provider);

  /////////////
  // Exceptions
  class MacroParseException : public BuildNG::Exception
  {
  public:
    MacroParseException(const Glib::ustring &msg)
      : BuildNG::Exception(msg)
    {
    }
  };

  class MacroReplaceException : public BuildNG::Exception
  {
  public:
    MacroReplaceException(const Glib::ustring &msg)
      : BuildNG::Exception(msg)
    {
    }
  };

  class MacroArgListExpectedException : public MacroReplaceException
  {
  public:
    MacroArgListExpectedException(const Glib::ustring &macro_name)
      : MacroReplaceException("The macro \"" + macro_name + "\" should be followed by an argument list.")
    {
    }
  };

  class SharpOperatorNeedsArgException : public MacroReplaceException
  {
  public:
    SharpOperatorNeedsArgException()
      : MacroReplaceException("The # operator must be followed by one of the macro's arguments.")
    {
    }
  };

  class ConcatOperatorNeedsOperandsException : public MacroReplaceException
  {
  public:
    ConcatOperatorNeedsOperandsException()
      : MacroReplaceException("The ## operator must be preceded and followed by an operand.")
    {
    }
  };
}

